/*
 * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 */
// 第六课 音频-扬声器和麦克风 I2s 音频编解码

#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "esp_system.h"
#include "esp_check.h"
#include "es8311.h"
#include "example_config.h"
#include "esp_efuse_table.h"


static const char *标签 = "i2s_es8311";                 // 标签 用于 ESP-IDF 中的日志输出
static const char err_reason[][30] = {"输入参数无效",
                                      "操作超时"
                                     };
static i2s_chan_handle_t tx_handle = NULL;              // tx_handle 是一个指向 i2s_chan_handle_t 类型的指针，用于表示 I2S 通道的发送句柄。
static i2s_chan_handle_t rx_handle = NULL;              // rx_handle 是一个指向 i2s_chan_handle_t 类型的指针，用于表示 I2S 通道的接收句柄。

/* 导入音乐文件作为缓冲区 */
#if CONFIG_EXAMPLE_MODE_MUSIC
extern const uint8_t music_pcm_start[] asm("_binary_canon_pcm_start");
extern const uint8_t music_pcm_end[]   asm("_binary_canon_pcm_end");
#endif

// 第六课 音频-扬声器和麦克风 I2s 音频编解码
static esp_err_t es8311_codec_init(void)        // 初始化ES8311音频编解码器
{
    /* 初始化I2C外设 */
#if !defined(CONFIG_EXAMPLE_BSP)
    const i2c_config_t es_i2c_cfg = {
        .sda_io_num = I2C_串行数据线,
        .scl_io_num = I2C_串行时钟线,
        .mode = I2C_MODE_MASTER,                //  I2C 模式为主模式
        .sda_pullup_en = GPIO_PULLUP_ENABLE,    // 启用SDA引脚的上拉电阻,GPIO_PULLUP_ENABLE
        .scl_pullup_en = GPIO_PULLUP_ENABLE,    // 启用SCL引脚的上拉电阻
        .master.clk_speed = 100000,              //  I2C总线时钟频率为100kHz
    };
    ESP_RETURN_ON_ERROR(i2c_param_config(I2C端口号, &es_i2c_cfg), 标签, "配置i2c失败");
    // i2c_param_config() 函数用于配置 I2C 外设的参数，并将其应用到 I2C 外设上。
    ESP_RETURN_ON_ERROR(i2c_driver_install(I2C端口号, I2C_MODE_MASTER,  0, 0, 0), 标签, "安装i2c驱动程序失败");
    // ESP_ERROR_CHECK() 宏用于检查函数调用的返回值是否为 ESP_OK，如果不是，则打印错误信息并中止程序执行。
#else
    ESP_ERROR_CHECK(bsp_i2c_init());            // 初始化 I2C 总线
#endif

    /* 初始化es8311编解码器 */
    es8311_handle_t es_handle = es8311_create(I2C端口号, ES8311_ADDRRES_0);
    ESP_RETURN_ON_FALSE(es_handle, ESP_FAIL, 标签, "es8311创建失败");
    const es8311_clock_config_t es_clk = {
        .mclk_inverted = false,
        .sclk_inverted = false,
        .mclk_from_mclk_pin = true,
        .mclk_frequency = 示例主时钟频率,
        .sample_frequency = 示例采样率
    };

    ESP_ERROR_CHECK(es8311_init(es_handle, &es_clk, ES8311_RESOLUTION_16, ES8311_RESOLUTION_16));
    // ESP_ERROR_CHECK() 宏用于检查函数调用的返回值是否为 ESP_OK，如果不是，则打印错误信息并中止程序执行。
    ESP_RETURN_ON_ERROR(es8311_sample_frequency_config(es_handle, 示例采样率 * 示例主时钟倍数, 示例采样率), 标签, "设置es8311采样频率失败");
    ESP_RETURN_ON_ERROR(es8311_voice_volume_set(es_handle, 示例语音音量, NULL), 标签, "设置es8311卷失败");
    ESP_RETURN_ON_ERROR(es8311_microphone_config(es_handle, false), 标签, "设置es8311麦克风失败");
#if CONFIG_EXAMPLE_MODE_ECHO
    ESP_RETURN_ON_ERROR(es8311_microphone_gain_set(es_handle, 示例麦克风增益), 标签, "设置es8311麦克风增益失败");
#endif
    return ESP_OK;
}

static esp_err_t i2s_driver_init(void)          //初始化I2S驱动程序
{
#if !defined(CONFIG_EXAMPLE_BSP)
    i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S端口号, I2S_ROLE_MASTER);
    chan_cfg.auto_clear = true; // 自动清除DMA缓冲区中的遗留数据
    ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle));
    i2s_std_config_t std_cfg = {
        .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(示例采样率),
        .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
        .gpio_cfg = {
            .mclk = I2S_主时钟线,
            .bclk = I2S_位时钟线,
            .ws = I2S_帧同步线,
            .dout = I2S_数据输出线,
            .din = I2S_数据输入线,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false,
            },
        },
    };
    std_cfg.clk_cfg.mclk_multiple = 示例主时钟倍数;

    ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_cfg));
    ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
    ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
#else
    ESP_LOGI(标签, "使用BSP进行硬件配置");
    i2s_std_config_t std_cfg = {
        .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(示例采样率),
        .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
        .gpio_cfg = BSP_I2S_GPIO_CFG,
    };
    std_cfg.clk_cfg.mclk_multiple = 示例主时钟倍数;
    ESP_ERROR_CHECK(bsp_audio_init(&std_cfg, &tx_handle, &rx_handle));
    ESP_ERROR_CHECK(bsp_audio_poweramp_enable(true));
#endif
    return ESP_OK;
}

#if CONFIG_EXAMPLE_MODE_MUSIC
static void i2s_music(void *args)               // 播放音乐
{
    esp_err_t ret = ESP_OK;
    size_t bytes_write = 0;                     // 用于存储写入数据的字节数     
    uint8_t *data_ptr = (uint8_t *)music_pcm_start;

    /* （可选）禁用TX通道并在启用TX通道之前预加载数据，以便立即传输有效数据 */
    ESP_ERROR_CHECK(i2s_channel_disable(tx_handle));
    ESP_ERROR_CHECK(i2s_channel_preload_data(tx_handle, data_ptr, music_pcm_end - data_ptr, &bytes_write));
    data_ptr += bytes_write;  // 向前移动数据指针

    /* 启用TX通道 */
    ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
    while (1) {
        /* 将音乐写入耳机 */
        ret = i2s_channel_write(tx_handle, data_ptr, music_pcm_end - data_ptr, &bytes_write, portMAX_DELAY);
        if (ret != ESP_OK) {
            /* 由于我们在i2s_channel_write中设置超时为portMAX_DELAY，所以除非你设置其他超时值，
            否则你不会到达这里，如果检测到超时，则表示写入操作失败。 */
            ESP_LOGE(标签, "[音乐]i2s写入失败， %s", err_reason[ret == ESP_ERR_TIMEOUT]);
            abort();
        }
        if (bytes_write > 0) {
            ESP_LOGI(标签, "[音乐]i2s音乐播放, %d 写入字节。", bytes_write);
        } else {
            ESP_LOGE(标签, "[音乐]i2s音乐播放失败。");
            abort();
        }
        data_ptr = (uint8_t *)music_pcm_start;
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    vTaskDelete(NULL);
}

#else
static void i2s_echo(void *args)
{
    int *mic_data = malloc(示例接收缓冲区大小);
    if (!mic_data) {
        ESP_LOGE(标签, "[echo]没有用于读取数据缓冲区的内存");
        abort();
    }
    esp_err_t ret = ESP_OK;
    size_t bytes_read = 0;
    size_t bytes_write = 0;
    ESP_LOGI(标签, "[回声]回声启动");

    while (1) {
        memset(mic_data, 0, 示例接收缓冲区大小);
        /* Read sample data from mic */
        ret = i2s_channel_read(rx_handle, mic_data, 示例接收缓冲区大小, &bytes_read, 1000);
        if (ret != ESP_OK) {
            ESP_LOGE(标签, "[回声]i2s读取失败, %s", err_reason[ret == ESP_ERR_TIMEOUT]);
            abort();
        }
        /* Write sample data to earphone */
        ret = i2s_channel_write(tx_handle, mic_data, 示例接收缓冲区大小, &bytes_write, 1000);
        if (ret != ESP_OK) {
            ESP_LOGE(标签, "[回声]i2s写入失败, %s", err_reason[ret == ESP_ERR_TIMEOUT]);
            abort();
        }
        if (bytes_read != bytes_write) {
            ESP_LOGW(标签, "[回声] %d 字节读取，但仅 %d 字节写入", bytes_read, bytes_write);
        }
    }
    vTaskDelete(NULL);
}
#endif

void app_main(void)
{
    printf("ESP电子熔丝VDD-SPI作为GPIO开始\n-----------------------------\n");
    //esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO);

    /* 初始化PA芯片NS4150B控制引脚 低电平关闭音频输出 高电平允许音频输出 */
    gpio_config_t io_conf = {
        .intr_type = GPIO_INTR_DISABLE, // 禁止中断
        .mode = GPIO_MODE_OUTPUT,       // 设置为输出模式
        .pin_bit_mask = 1 << 13,        // 引脚的位掩模
        .pull_down_en = 0,              // 禁用下拉模式
        .pull_up_en = 1,                // 启用上拉模式
    };
    //使用给定设置配置GPIO
    gpio_config(&io_conf);

    gpio_set_level(GPIO_NUM_13, 1); // 输出高电平

    printf("i2s es8311编解码器示例启动\n-----------------------------\n");
    /* 初始化i2s外设 */
    if (i2s_driver_init() != ESP_OK) {
        ESP_LOGE(标签, "i2s驱动初始化失败");
        abort();
    } else {
        ESP_LOGI(标签, "i2s驱动初始化成功");
    }
    /* 初始化i2c外设并通过i2c配置es8311编解码器 */
    if (es8311_codec_init() != ESP_OK) {
        ESP_LOGE(标签, "es8311编解码器初始化失败");
        abort();
    } else {
        ESP_LOGI(标签, "es8311编解码器初始化成功");
    }
    
#if CONFIG_EXAMPLE_MODE_MUSIC
    /* 在音乐模式下播放一段音乐 */
    xTaskCreate(i2s_music, "i2s_music", 4096, NULL, 5, NULL);
#else
    /* 在回声模式下回声来自MIC的声音 */
    xTaskCreate(i2s_echo, "i2s_echo", 8192, NULL, 5, NULL);
#endif
}
